|
1
|
|
|
// Type definitions for object types and non-sortable types |
|
2
|
|
|
type ObjectType = Record<string | symbol, unknown>; |
|
3
|
|
|
type SortedEntry = [string | symbol, unknown]; |
|
4
|
|
|
type NonSortableType = |
|
5
|
|
|
| Date |
|
6
|
|
|
| RegExp |
|
7
|
|
|
| (() => unknown) |
|
8
|
|
|
| ((...args: unknown[]) => unknown) |
|
9
|
|
|
| Error |
|
10
|
|
|
| Map<unknown, unknown> |
|
11
|
|
|
| Set<unknown> |
|
12
|
|
|
| WeakMap<object, unknown> |
|
13
|
|
|
| WeakSet<object> |
|
14
|
|
|
| Promise<unknown>; |
|
15
|
|
|
|
|
16
|
|
|
// Main sorting function that can handle various data types |
|
17
|
1 |
|
export function sort<T>( |
|
18
|
|
|
data: T, |
|
19
|
|
|
ascending = true, |
|
20
|
|
|
sortPrimitiveArrays = false |
|
21
|
|
|
): T { |
|
22
|
40 |
|
return sortRecursively(data, ascending, sortPrimitiveArrays); |
|
23
|
|
|
} |
|
24
|
|
|
|
|
25
|
|
|
// Recursive function to sort nested structures |
|
26
|
|
|
function sortRecursively<T>( |
|
27
|
|
|
data: T, |
|
28
|
|
|
ascending: boolean, |
|
29
|
|
|
sortPrimitiveArrays: boolean |
|
30
|
|
|
): T { |
|
31
|
201 |
|
if (!isObject(data) || Array.isArray(data) || isNonSortableObject(data)) { |
|
32
|
157 |
|
if (Array.isArray(data)) { |
|
33
|
|
|
// If sortPrimitiveArrays is true and all items are primitives, sort the array |
|
34
|
39 |
|
if ( |
|
35
|
|
|
sortPrimitiveArrays && |
|
36
|
|
|
data.length > 0 && |
|
37
|
65 |
|
data.every((item) => isPrimitive(item)) |
|
38
|
|
|
) { |
|
39
|
16 |
|
return [...data].sort((a, b) => { |
|
40
|
60 |
|
if (typeof a === 'string' && typeof b === 'string') { |
|
41
|
9 |
|
return ascending ? a.localeCompare(b) : b.localeCompare(a); |
|
42
|
|
|
} |
|
43
|
51 |
|
if (typeof a === 'number' && typeof b === 'number') { |
|
44
|
36 |
|
return ascending ? a - b : b - a; |
|
45
|
|
|
} |
|
46
|
15 |
|
if (typeof a === 'boolean' && typeof b === 'boolean') { |
|
47
|
10 |
|
return ascending |
|
48
|
|
|
? a === b |
|
49
|
|
|
? 0 |
|
50
|
|
|
: a |
|
51
|
|
|
? 1 |
|
52
|
|
|
: -1 |
|
53
|
|
|
: a === b |
|
54
|
|
|
? 0 |
|
55
|
|
|
: a |
|
56
|
|
|
? -1 |
|
57
|
|
|
: 1; |
|
58
|
|
|
} |
|
59
|
|
|
// For mixed types or other primitives, maintain original order |
|
60
|
5 |
|
return 0; |
|
61
|
|
|
}) as T; |
|
62
|
|
|
} |
|
63
|
|
|
// Otherwise, recursively sort array items |
|
64
|
23 |
|
return data.map((item) => |
|
65
|
58 |
|
sortRecursively(item, ascending, sortPrimitiveArrays) |
|
66
|
|
|
) as T; |
|
67
|
|
|
} |
|
68
|
118 |
|
return data; |
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
44 |
|
return sortObject(data as ObjectType, ascending, sortPrimitiveArrays) as T; |
|
72
|
|
|
} |
|
73
|
|
|
|
|
74
|
|
|
// Helper function to check if a value is an object |
|
75
|
|
|
function isObject(data: unknown): data is ObjectType { |
|
76
|
201 |
|
return typeof data === 'object' && data !== null; |
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
function isPrimitive(data: unknown): boolean { |
|
80
|
65 |
|
return ( |
|
81
|
|
|
typeof data === 'string' || |
|
82
|
|
|
typeof data === 'number' || |
|
83
|
|
|
typeof data === 'boolean' |
|
84
|
|
|
); |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
// Function to sort object properties |
|
88
|
|
|
function sortObject( |
|
89
|
|
|
obj: ObjectType, |
|
90
|
|
|
ascending: boolean, |
|
91
|
|
|
sortPrimitiveArrays: boolean |
|
92
|
|
|
): ObjectType { |
|
93
|
44 |
|
const entries = [ |
|
94
|
|
|
...Object.entries(obj), |
|
95
|
|
|
...Object.getOwnPropertySymbols(obj).map( |
|
96
|
2 |
|
(sym) => [sym, obj[sym]] as SortedEntry |
|
97
|
|
|
), |
|
98
|
|
|
]; |
|
99
|
|
|
|
|
100
|
44 |
|
const sortedEntries = entries.sort(([keyA], [keyB]) => { |
|
101
|
81 |
|
if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0; |
|
102
|
80 |
|
if (typeof keyA === 'symbol') return 1; |
|
103
|
77 |
|
if (typeof keyB === 'symbol') return -1; |
|
104
|
77 |
|
return ascending |
|
105
|
|
|
? (keyA as string).localeCompare(keyB as string) |
|
106
|
|
|
: (keyB as string).localeCompare(keyA as string); |
|
107
|
|
|
}); |
|
108
|
|
|
|
|
109
|
44 |
|
return Object.fromEntries( |
|
110
|
103 |
|
sortedEntries.map(([key, value]) => [ |
|
111
|
|
|
key, |
|
112
|
|
|
sortRecursively(value, ascending, sortPrimitiveArrays), |
|
113
|
|
|
]) |
|
114
|
|
|
); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
// Helper function to check if an object is of a non-sortable type |
|
118
|
|
|
function isNonSortableObject(obj: unknown): obj is NonSortableType { |
|
119
|
61 |
|
const nonSortableTypes = [ |
|
120
|
|
|
Date, |
|
121
|
|
|
RegExp, |
|
122
|
|
|
Function, |
|
123
|
|
|
Error, |
|
124
|
|
|
Map, |
|
125
|
|
|
Set, |
|
126
|
|
|
WeakMap, |
|
127
|
|
|
WeakSet, |
|
128
|
|
|
Promise, |
|
129
|
|
|
]; |
|
130
|
61 |
|
return ( |
|
131
|
459 |
|
nonSortableTypes.some((type) => obj instanceof type) || |
|
132
|
|
|
Symbol.iterator in Object(obj) |
|
133
|
|
|
); |
|
134
|
|
|
} |
|
135
|
|
|
|